package hotel.quartz.oversea.job.task;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import hotel.base.oversea.constant.ApiConstans;
import hotel.base.oversea.constant.CommonConstants;
import hotel.base.oversea.constant.CommonUrlConstants;
import hotel.base.oversea.constant.MeituanConstants;
import hotel.base.oversea.constant.enums.MtHotelEnums;
import hotel.base.oversea.constant.quartz.UrlMtEnums;
import hotel.base.oversea.utils.Result;
import hotel.quartz.oversea.config.service.RedisService;
import hotel.quartz.oversea.service.JobAndTriggerService;
import hotel.quartz.oversea.util.JobRestTemplateUtils;
import hotel.quartz.oversea.util.MapUtils;
import hotel.quartz.oversea.vo.meituan.MtHotelDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Slf4j
@Component
public class MeituanHotelDetailJobTask {
    private String jobGroupName = MeituanConstants.JOB_GROUP_NAME_MT;
    private String jobClassName = this.getClass().getName();
    private int idsPageSize = ApiConstans.MT_SELECT_PAGE_SIZE;
    private String timestamp;
    private String thisIdsMapTaskRedisKey;
    private int waitCount;

    private String redisHotelDetailWaitDataSaveKey = MeituanConstants.REDIS_MT_HOTEL_DATA_WAIT_SAVE;
    private String redisNowIds = MeituanConstants.REDIS_MT_HOTEL_DETAIL_JOB_IDS_NOW;
    private String redisIdsHas = MeituanConstants.REDIS_MT_HOTEL_DETAIL_JOB_IDS_HAS;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private JobAndTriggerService jobAndTriggerService;
    @Autowired
    private RedisTemplate redisTemplate;

    public void setThisIdsMapTaskRedisKey(String thisIdsMapTaskRedisKey){
        this.thisIdsMapTaskRedisKey = thisIdsMapTaskRedisKey;
        this.redisHotelDetailWaitDataSaveKey = thisIdsMapTaskRedisKey+ MeituanConstants.REDIS_MT_HOTEL_DATA_WAIT_SAVE;
        this.redisNowIds = thisIdsMapTaskRedisKey+ MeituanConstants.REDIS_MT_HOTEL_DETAIL_JOB_IDS_NOW;
        this.redisIdsHas = thisIdsMapTaskRedisKey+ MeituanConstants.REDIS_MT_HOTEL_DETAIL_JOB_IDS_HAS;
        this.timestamp = thisIdsMapTaskRedisKey+"_Mhdjt"+String.valueOf(System.currentTimeMillis());
    }
    //@Async 标识这个方法异步执行，可返回void或Future
    @Async
    public void execute(String setThisIdsMapTaskRedisKey,String accessToken){
        //接口限制1000 300000
        log.info("Meituan Hotel Detail Job {} >>>>>>>>> 酒店详情接口执行时间: {}", timestamp, DateUtil.now());
        Map idsMap = (Map)redisService.get(setThisIdsMapTaskRedisKey);
        if(null != idsMap && idsMap.size()>0){
            setThisIdsMapTaskRedisKey(setThisIdsMapTaskRedisKey);
            //正式：ip+端口+网关+mapping地址?access_token//本地联调：ip+端口+mapping地址?access_token
            String host = CommonUrlConstants.MEITUAN_HOST;
            //美团接口，获取酒店详情
            String hotelDetailUrl = host+ UrlMtEnums.hotelDetail.GET.getValue();// +"?access_token="+accessToken;
            //美团接口，保存酒店详情
            String saveHotelInfosUrl = host+ UrlMtEnums.hotelDetail.SAVE.getValue();// +"?access_token="+accessToken;
            //判断缓存的酒店详情数据是否已大量需要保存进数据库(解决数据库事务锁问题，达到量再insert数据)
            boolean check = checkSaveRedisHotelInfos(saveHotelInfosUrl);
            if(!check){
                List<Long> hotelIds = getNowIds();
                if(null != hotelIds && hotelIds.size()>0){
                    requestHotelDetailApi(hotelIds,hotelDetailUrl);
                }
            }
        }
        log.info("Meituan Hotel Detail Job {} >>>>>>>>> 酒店详情接口结束时间: {},{}",timestamp, DateUtil.now());
    }

    /**
     * 校验是否需要保存缓存中的酒店数据
     * @return
     */
    private boolean checkSaveRedisHotelInfos(String url) {
        log.info("Meituan Hotel Detail Job {} 酒店详情接口进入方法 checkSaveRedisHotelInfos",timestamp);
        Map<String,List<MtHotelDetailVo>> getInfos = getRedisHotelWaitInfos();
        if(null != getInfos && getInfos.size()>=waitCount){
            //缓存酒店数据达到大于等于五次，则执行保存到数据库操作，可能大于五次比如数据操作保存失败还原加入到redis
            boolean result = requestSaveRedisWaitHotelInfosApi(url,getInfos);
            if(result){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

    /**
     * 请求保存酒店数据api
     * controller接收是 Long,List<> -->但上送要是Map<String,List<MtHotelDetailVo>>，long-->string json格式化
     * @return
     */
    private boolean requestSaveRedisWaitHotelInfosApi(String url,Map<String,List<MtHotelDetailVo>> redisHotelInfosMap) {
        log.info("Meituan Hotel Detail Job {} 酒店详情接口进入方法 requestSaveRedisWaitHotelInfos",timestamp);
        if(null == redisHotelInfosMap){
            //getRedisHotelRoomWaitData 有串行的可能，上次执行还没结束，这次执行，上次删除getRedisHotelRoomWaitData，这次进入提交getRedisHotelRoomWaitData，
            // 但是getRedisHotelRoomWaitData为空，所以返回false
            log.info("Meituan Hotel Detail Job {} 酒店详情接口进入方法 requestSaveRedisWaitHotelInfos  redisHotelInfosMap 数据为空，不执行上送接口: {}",timestamp);
            return false;
        }
        Map map = new HashMap();
        map.put("redisHotelInfosMap",redisHotelInfosMap);
        //调用前,删除当前缓存的酒店数据，让下一次定时器正常查询上游接口。若调用结果失败则还原加入缓存
        deleteRedisWaitSaveHotelInfos();
        //发起请求
        Result response = JobRestTemplateUtils.post(url, JSON.toJSONString(map),restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            log.info("Meituan Hotel Detail Job {} requestSaveRedisWaitHotelInfos 保存数据成功",timestamp);
            return true;
        }else{
            log.error("Meituan Hotel Detail Job {} requestSaveRedisWaitHotelInfos 异常错误: {}",timestamp,response);
            backPutRedisHotelInfos(redisHotelInfosMap);
            return false;
        }
    }

    /**
     * 获取当前需查询ids
     * 缓存中 获取所有ids
     * 减去已查询的ids
     * 剩余待查询的ids提取20个(idsPageSize个)
     * @return
     */
    public List<Long> getNowIds(){
        log.info("Meituan Hotel Detail Job {} 酒店详情接口进入方法 getNowIds",timestamp);
        Map<String,Object> nowIds = getRedisNowHotelIds();
        if(null == nowIds || nowIds.size()==0){
            nowIds = new HashMap<>();
            //重新获取
            Map<String,Object> allIdsMap = getRedisAllHotelIds();
            if(null != allIdsMap && allIdsMap.size()>0){
                Map<String,Object> hasCityIdsMap = getRedisHasHotelIds();

                if(null == hasCityIdsMap){
                    int i = 1;
                    for (Map.Entry<String, Object> entry : allIdsMap.entrySet()) {
                        if(i<=idsPageSize){
                            nowIds.put(entry.getKey(),entry.getValue());
                        }else{
                            break;
                        }
                        i += 1;
                    }
                }else{
                    if(null != allIdsMap && allIdsMap.size()>0){
                        int i = 1;
                        Map<String, Object> resultMap = MapUtils.getDifferenceSet(allIdsMap,hasCityIdsMap);
                        if(null != resultMap && resultMap.size()>0){
                            for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
                                if(i<=idsPageSize){
                                    nowIds.put(entry.getKey(),entry.getValue());
                                }else{
                                    break;
                                }
                                i += 1;
                            }
                        }else{
                            log.info("Meituan Hotel Detail Job {} 酒店详情接口 getDifferenceSet is null");
                            endingJob(true);
                            return null;
                        }

                    }
                }
                putHasIdsMap(nowIds);
                setRedisNowHotelIds(nowIds);
            }else{
                endingJob(false);
                log.error("Meituan Hotel Detail Job {} 暂停定时器！携程子酒店id获取异常错误,getRedisAllHotelIds is null",timestamp);
                return null;
            }
        }else{
        }
        List<Long> list = new ArrayList<>();
        for(Map.Entry<String,Object> entry:nowIds.entrySet()){
            list.add(Long.valueOf(entry.getKey()));
        }
        log.info("Meituan Hotel Detail Job {} 进入方法 getNowIds，返回：{}",timestamp, JSON.toJSONString(list));
        return list;
    }

    /**
     * 加入 某酒店ids被查询标识
     * @param nowIds
     */
    public void putHasIdsMap(Map nowIds){
        Map<String,Object> has = getRedisHasHotelIds();
        if(null == has){
            has = new HashMap<>();
        }
        has.putAll(nowIds);
        setRedisHasHotelIds(has);
        log.info("Switchs Hotel Master Job {} 酒店物理房型 putHasIdsMap has.size:{}",timestamp,has.size());
    }

    /**
     * 移出 某酒店ids被查询标识
     * @param idsMap
     */
    public void removeHasIdsMap(Map<String,Object> idsMap){
        Map<String,Object> has = getRedisHasHotelIds();
        if(null != has){
            for(Map.Entry<String,Object> entry:idsMap.entrySet()){
                has.remove(entry.getKey());
            }
        }
        setRedisHasHotelIds(has);
        log.info("Switchs Hotel Master Job {} 酒店物理房型 removeHasIdsMap has.size:{}",timestamp,has.size());
    }

    /**
     * 请求酒店详情接口，支持最多20个酒店
     * 根据酒店数组ids查询酒店详情，response没有data则为异常
     */
    public void requestHotelDetailApi(List<Long> ids,String detailUrl){
        Map paramsMap = new HashMap();
        paramsMap.put("strategy", MtHotelEnums.detailStrategyEnum.ALL.getCode());
        paramsMap.put("hotelIds",ids);
        String detailParamsJson = JSON.toJSONString(paramsMap);
        log.info("Meituan Hotel Detail Job {} 酒店详情进入方法 requestDetailApi: {}",timestamp,detailParamsJson);
        //发起请求
        Result response = JobRestTemplateUtils.post(detailUrl, JSON.toJSONString(paramsMap),restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS && null!= response.getData()){
            String hotelDetails = JSON.toJSONString(response.getData());
            List<MtHotelDetailVo> voList = null;
            try {
                voList = JSON.parseArray(hotelDetails, MtHotelDetailVo.class);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
            if (null != voList && voList.size() > 0) {
                //一定会有数据，有酒店ids要有对应数据，存入redis
                boolean setResultRedis = savePutRedisHotelInfos(voList);
                if(setResultRedis){
                    nowSuccessTodo();
                }else{
                    removeHasIdsMap(getRedisNowHotelIds());
                    //异常，不更新数据，下一次定时器继续执行
                    log.error("Meituan Hotel Detail Job {} 酒店详情进入方法 savePutRedisHotelInfos 缓存set异常错误，缓存失败！当前次定时器执行无效！",timestamp);
                }
            } else {
                removeHasIdsMap(getRedisNowHotelIds());
                //异常，不更新数据，下一次定时器继续执行
                log.error("Meituan Hotel Detail Job {} 获取酒店详情异常错误 voList null: {},{}",timestamp,response,detailParamsJson);
            }
        }else{
            //更新保存详情不成功时，不更新index，定时器再次执行的还是上一次数据
            removeHasIdsMap(getRedisNowHotelIds());
            log.error("Meituan Hotel Detail Job {} 获取酒店详情返回失败: {},{}",timestamp,response,detailParamsJson);
        }
    }

    /**
     * 更新redis已操作的ids，判断是否结束定时器，
     */
    public void nowSuccessTodo(){
        deleteRedisNowHotelIds();
        //判断是否已无下一个需要查询，则暂停定时器
        Map<String,Object> allCityIdsMap = getRedisAllHotelIds();
        Map<String, Object> resultMap = MapUtils.getDifferenceSet(allCityIdsMap,getRedisHasHotelIds());
        if(null == resultMap || resultMap.size()==0) {
            //已无待查询
            endingJob(true);
        }
    }
    /**
     * 结束定时器
     */
    public void endingJob(boolean key){
        //已最后一页，暂停定时器
        /*JobForm jobForm = new JobForm();
        jobForm.setJobClassName(jobClassName);
        jobForm.setJobGroupName(jobGroupName);
        try {
            jobAndTriggerService.pauseJob(jobForm);
            log.warn("Meituan Hotel Detail Job 执行暂停！: {},{}",jobGroupName,jobClassName);
            if(key){
                deleteRedisHasIds();
                deleteRedisNowHotelIds();
                redisTemplate.delete(redisHotelDetailWaitDataSaveKey);
                redisTemplate.delete(thisIdsMapTaskRedisKey);
            }
        } catch (SchedulerException e) {
            log.error(e.getMessage(),e);
        }*/
        deleteRedisHasIds();
        deleteRedisNowHotelIds();
        redisTemplate.delete(redisHotelDetailWaitDataSaveKey);
        redisTemplate.delete(thisIdsMapTaskRedisKey);
        log.warn("Meituan Hotel Detail Job 当前task执行结束！记录结束标识: {}",thisIdsMapTaskRedisKey);
        //redisTemplate.opsForValue().set("quartz_mt_hotel_detail_job_task_done_"+thisIdsMapTaskRedisKey,1);
        /*if(null == redisTemplate.opsForValue().get(redisKeyTaskingCountTemp)){
            redisTemplate.opsForValue().set(redisKeyTaskingCountTemp,getRedisKeyTaskingCount());
        }*/
    }

    /**
     * 成功请求酒店详情接口后，合法酒店数据，追加进缓存中
     * @param voList
     */
    public boolean savePutRedisHotelInfos(List<MtHotelDetailVo> voList){
        Map<String,List<MtHotelDetailVo>> getRedisMap = (Map<String,List<MtHotelDetailVo>>)redisService.get(redisHotelDetailWaitDataSaveKey);
        Map<String,List<MtHotelDetailVo>> newRedisMap = new HashMap<>();
        newRedisMap.put(UUID.randomUUID().toString(),voList);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(newRedisMap);
        }else{
            getRedisMap = newRedisMap;
        }
        boolean result = redisService.set(redisHotelDetailWaitDataSaveKey,getRedisMap);
        return result;
    }

    /**
     * 获取缓存的待保存酒店数据
     * @return
     */
    public Map<String,List<MtHotelDetailVo>> getRedisHotelWaitInfos(){
        return (Map<String,List<MtHotelDetailVo>>)redisService.get(redisHotelDetailWaitDataSaveKey);
    }
    /**
     * 删除缓存的待保存酒店数据
     * @return
     */
    public void deleteRedisWaitSaveHotelInfos(){
        log.info("Meituan Hotel Detail Job {} 酒店详情进入方法 deleteRedisHotelInfos 删除当前酒店数据缓存 REDIS_MT_HOTEL_DATA_WAIT_SAVE",timestamp);
        redisTemplate.delete(redisHotelDetailWaitDataSaveKey);
    }
    /**
     * 还原追加回去待保存酒店数据
     * @return
     */
    public void backPutRedisHotelInfos(Map<String,List<MtHotelDetailVo>> redisHotelInfosMap){
        log.info("Meituan Hotel Detail Job {} 酒店详情进入方法 backPutRedisHotelInfos 还原追加当前酒店数据缓存 REDIS_MT_HOTEL_DATA_WAIT_SAVE",timestamp);
        //还原redis数据
        Map<String,List<MtHotelDetailVo>> getRedisMap = (Map<String,List<MtHotelDetailVo>>)redisService.get(redisHotelDetailWaitDataSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(redisHotelInfosMap);
        }else{
            getRedisMap = redisHotelInfosMap;
        }
        redisService.set(redisHotelDetailWaitDataSaveKey,getRedisMap);
    }

    public void deleteRedisNowHotelIds(){
        redisTemplate.delete(timestamp+redisNowIds);
    }
    public void deleteRedisHasIds(){
        redisTemplate.delete(redisIdsHas);
    }

    /**
     * 每次执行为5秒，会有串行的时候，则有些rediskey作为标识时，需要是唯一标识，不然会两次执行之间产生获取标识的冲突：
     * 比如 上次执行和这次执行串了，上次结束执行并delete掉nowIds的redis缓存，这次执行需要获取nowIds并putAll进去hasIds则会报错NullPointerException
     * 需要唯一的redis缓存对象，特殊处理，当前优化加上timestamp
     * @param newIds
     */
    public void setRedisNowHotelIds(Map<String,Object> newIds){
        redisTemplate.opsForValue().set(timestamp+redisNowIds,newIds,3600L);
    }
    public void setRedisHasHotelIds(Map<String,Object> updateHas){
        redisTemplate.opsForValue().set(redisIdsHas,updateHas);
    }
    public Map<String,Object> getRedisNowHotelIds(){
        return (Map<String,Object>)redisTemplate.opsForValue().get(timestamp+redisNowIds);
    }
    public Map<String,Object> getRedisAllHotelIds(){
        return (Map<String,Object>)redisTemplate.opsForValue().get(thisIdsMapTaskRedisKey);
    }
    public Map<String,Object> getRedisHasHotelIds(){
        return (Map<String,Object>)redisTemplate.opsForValue().get(redisIdsHas);
    }


}
